Galileo Computing <openbook>
Galileo Computing - Programming the Net
Galileo Computing - Programming the Net

...powered by www.netzwerkartist.de...

Java 2 von Friedrich Esser
Designmuster und Zertifizierungswissen
Zum Katalog
gp Kapitel 15 Java Foundation Classes
  gp 15.1 Vorbemerkungen
  gp 15.2 Begriffe
  gp 15.3 Top-Level-Container
  gp 15.4 Layout-Manager
  gp 15.5 Ereignisbehandlung
  gp 15.6 MFC-Architektur
  gp 15.7 Multi-Threading in Swing
  gp 15.8 Applet
  gp 15.9 Zusammenfassung
  gp 15.10 Chamäleon-Architektur

Kapitel 15 Java Foundation Classes

Unter Java Foundation Classes, kurz JFC, sind mehrere Packages zusammengefasst, die für verschiedene Aufgaben APIs bereitstellen.
Die Ereignisbehandlung und grundlegende Grafik-Komponenten sind im AWT (Abstract Windowing Toolkit) enthalten.
Auf dem AWT bauen 2D-Graphik, Swing als plattformunabhängiges GUI (Graphical User Interfaces) sowie diverse APIs zum Drucken, Drag&Drop und Accessibility für die behindertengerechte Ein- und Ausgabe auf.
Der Gesamtkonzeption folgend werden die grundlegenden JVC-Mechanismen und -Muster behandelt.


Galileo Computing

15.1 Vorbemerkungen  downtop

Das AWT gibt es bereits seit Java 1.0. Mit Java 1.1 wurde ein anderes Ereignis-Modell und mit Java 1.2 dann eine verbesserte 2D-Graphik mit Namen Java 2D und die Benutzerschnittstelle Swing eingeführt.

Vor allem wurde Swing als Meilenstein einer neuen GUI-Generation gefeiert, da es unabhängig von dem GUI-API des Betriebssystems eine komplette graphische Benutzerschnittstelle mit vielen Komponenten bereitstellt. Als herausragende Features von Swing werden

Pluggable Look & Feels

gp  eine anpassbare graphische Oberfläche (pluggable Look-and-Feels)

Lightweight
Components

gp  so genannte leichtgewichtige Komponenten (lightweight Components)

Model-View-
Controller

gp  eine MVC-Architektur (Model-View-Controller)

angesehen.

Paradigma der heterogenen IT:
lose Kopplung auf Basis universeller Standards

Nachdem sich nun die Euphorie um Swing gelegt hat, wird ihr Stellenwert sowie Stärken und Schwächen klarer.

In einer verteilten, heterogenen IT-Welt, in der Applikationen auf verschiedenen Ebenen und Systemen laufen, ist eine möglichst lose Kopplung zwischen Servern und Clients essenziell.

gp  Kommunikation zwischen Servern und Clients funktioniert dann am besten, wenn beide möglichst wenig Annahmen über die Fähigkeiten des anderen zugrunde legen.

Swing ist
ressourcen-intensiv

Damit Swing als Client-GUI ein für Benutzer akzeptables Verhalten zeigt, benötigt es entsprechende Ressourcen, schnelle Prozessoren und einen swing-konformen Browser.

Internet-Anwendungen können diese o.a. Annahmen kaum über Clients machen, was impliziert:

Swing vs. Internet und Markup-Sprachen

gp  Nur in unternehmensweiten Applikationen (Intranet) ist eine enge Integration bzw. ein GUI wie Swing auf Seiten der Clients sinnvoll.
gp  Für Internet bzw. ressourcenschonende Applikationen, die gleichermaßen von PDAs, PCs und Workstations genutzt werden, sind Techniken, die auf Markup-Sprachen beruhen, besser geeignet.

Denn im ersten Fall liegt die Aufgabe der grafischen Darstellung und Interaktion mit dem Benutzer eindeutig beim (Java-)Programmierer. Er wird bei Einsatz von Swing feste Annahmen über die Client-Maschinen machen müssen.

Da Maschinen und Betriebssystem somit bekannt sind, konkurriert Swing dann auch mit proprietären Lösungen wie z.B. WFC .

Frontend: Domäne der Nicht-/BindestrichInformatiker

Im zweiten Fall liegt die Benutzer-Interaktion in der Regel in den Händen von Grafik-Designern, bestenfalls Bindestrich-Informatikern.

gp  Frontend-Ersteller - nicht unbedingt Informatiker - bevorzugen eindeutig auch Frontend-Werkzeuge, die automatisch Markup-Code erzeugen, in den dann die Funktionalität bzw. Interaktion mit dem Server möglichst transparent eingebettet werden soll.

Swing konkurriert daher mit verschiedenen anderen Lösungen, die natürlich auch in Java zur Verfügung stehen. Hierzu zählen u.a. Java Server Pages und Packages zur XML-Unterstützung.

Swing: eine interessante Design-Studie

Abgesehen davon sind aber Ereignisverarbeitung, Komponenten-, Layout- oder MVC-Techniken nicht uninteressant, da sie auf Pattern und Basiswissen beruhen, deren Kenntnis für andere Bereiche durchaus nützlich sein kann.

gp  Die Intention der nachfolgenden JFC-Betrachtung ist neben der Vermittlung absolut notwendiger Grundlagen das Kennenlernen von Konzepten, die übergreifend auch für andere Bereiche interessant sind.

Grundlagen, Beispiele und Konzepte werden fast ausschließlich anhand der neuen APIs vorgestellt.


Galileo Computing

15.2 Begriffe  downtop

Um in Java graphische Anwendungen zu schreiben, sind folgende Begriffe hilfreich.

Component

gp  Component umfasst alle möglichen graphischen Komponenten, angefangen von dem obersten Applikations- bzw. Applet-Fenster bis hin zu spezialisierten Komponenten für Passwörter, Bilder oder Zeichnungen.

Heavyweight Component

gp  Heavyweight Components sind Komponenten, die direkt an Fenster des Betriebssystems gebunden sind. Damit ist das Look-and-Feel (Form und Verhalten) dieser Komponenten ebenfalls an das Betriebssystem gebunden.

Lightweight
Component

gp  Lightweight Components sind Komponenten, die mit Hilfe von 2D-Graphikroutinen innerhalb von heavyweight Komponenten gezeichnet werden. Aussehen und Verhalten sind damit unabhängig vom Betriebssystem.

Container

gp  Container sind spezielle Komponenten, die andere Komponenten, d.h. auch andere Container, aufnehmen können.

Layout-Manager

gp  Layout-Manager übernehmen die Anordnung der Komponenten in einem Container. Es gibt verschiedene vordefinierte Layout-Manager, die entweder dem Container fest zugeordnet oder frei wählbar bzw. auch implementierbar sind.

Top-Level-
Container

gp  Top-Level-Container bezeichnet diejenigen Container, die als äußeres Fenster gewählt werden können.

AWT 1.0:
heavyweight

Das AWT von Java 1.0 besteht nur aus heavyweight Components. Damit wurde das Aussehen z.B. einer Schaltfläche oder Liste fest an Windows, Motif oder Macintosh gebunden.

Bei Swing wird es als Vorteil angesehen, jederzeit auf einer Plattform alle anderen Look-and-Feels emulieren zu können.

Swing: awaiting the GigaHertz-World

Die Kehrseite ist klar. Swing ist äußerst ressourcenintensiv und selbst auf schnellen CPUs nicht sehr reaktiv. Dies ist der Preis der flexiblen Konzeption, die sich in Hunderten von Klassen mit einem komplexen Beziehungsgeflecht und Mustern kristallisiert.


Galileo Computing

15.3 Top-Level-Container  downtop

Top-Level-Container
mit native Peer

Ausgangspunkt jeder graphischen Anwendung ist ein Top-Level-Container, der als äußere Komponente heavyweight ist und damit einen native Peer - ein gleichartiges Fenster der Plattform - hat.

In diesen Container werden dann Subcontainer und graphische Komponenten eingefügt (siehe Abb. 15.1).

Klassen-Struktur der Komponenten


Abbildung
Abbildung 15.1   Logischer Komponenten-Aufbau

(J)Applet/Dialog/Frame/Window

Die Top-Level-Container im »alten« AWT sind Applet, Dialog, Frame und Window, die in Swing JApplet, JDialog, JFrame und JWindow.

Im AWT waren allerdings gewisse Ungereimtheiten enthalten, die einer einfachen Erweiterung entgegenstanden. Unter anderem wurden Menü-Komponenten nicht von Component abgeleitet.

JComponent:
Basisklasse in Swing

Eine weitere Schwierigkeit besteht darin, dass in Swing alle Komponenten von JComponent abgeleitet werden, um die erweiterten Eigenschaften zu nutzen.

Ausnahme:
Root-Container

Natürlich bilden die Top-Level-Container von Swing eine Ausnahme, da sie bereits von ihren AWT-Pendants abgeleitet wurden. Also muss zwangsläufig ein einzelnes inneres Objekt JRootPane in jedem Root-Container von Swing erzeugt werden, das sich dann - der neuen Swing-Hierarchie folgend - von JComponent ableitet.

Damit sieht die Klassen-Hierarchie nicht mehr ganz so einfach aus (siehe Abb.15.2).

Klassen-Hierarchie der Root-Container


Abbildung
Abbildung 15.2   Packages und Hierarchie der Root-Container

Interessant an der Hierarchie ist, dass alle Swing-Komponenten auch Container sind. Aber nur wenige werden tatsächlich als Container benutzt.

JInternalFrame:
virtueller Top-Level-Container

Des Weiteren gibt es noch einen lightweight Container JInternalFrame, der sich wie ein Top-Level-Container verhält, allerdings nicht als äußeres Fenster verwendet werden kann. Da er von keinem AWT-Container abgeleitet ist, ist JInternalFrame Subklasse von JComponent.

MDI-
Unterstützung

Zusammen mit JDesktopPane, einem Container für JInternalFrame, unterstützt somit Swing auch MDI (Multiple Document Interface), d.h., es können komplette Fenster in Fenster eingebettet werden.


Galileo Computing

15.3.1 JRootPane  downtop

Die fünf Root-Container, d.h. die vier Top-Level-Container und JInternalFrame, enthalten genau eine Instanz von JRootPane, die automatisch erzeugt wird.

JRootPane =
glassPane + layeredPane

JRootPane ist ein Container mit einem festen Aufbau. Er setzt sich aus einer Komponente, glassPane, und layeredPane, Instanz der Klasse JLayeredPane, zusammen.

glassPane ist per Default eine Instanz von JPanel, liegt über den anderen Komponenten und fängt die Ereignisse der Maus ab. Allerdings könnte es als beliebige Komponente auch zum Zeichnen eingesetzt werden.

Wie bereits der Name sagt, wird JLayeredPane dazu benutzt, mehrere Komponenten, die sich möglicherweise überlappen, in einer Reihenfolge anzuordnen (siehe 15.3.2).

layeredPane =
menuBar + contentPane

Die layeredPane enthält in JRootPane allerdings nur die beiden Komponenten menuBar, Instanz von JMenuBar, und einen Container contentPane. Der Container contentPane ist per Default ein JPanel und nimmt die eigentlichen Komponenten des Fensters auf (siehe Abb. 15.3).

Anordnung in JRootPane


Abbildung
Abbildung 15.3   Aufbau von JRootPane

Von allen aufgeführten Komponenten ist nur die Menüleiste optional.

Interface
RootPaneContainer

Das Interface RootPaneContainer wird von allen Root-Containern implementiert und enthält Zugriffs-Methoden auf JRootPane und seine internen Komponenten (mit Ausnahme der optionalen Menüleiste).

interface RootPaneContainer {
  JRootPane    getRootPane();
  Component    getGlassPane();
  JLayeredPane getLayeredPane();
  Container    getContentPane();
  void setGlassPane  (Component glassPane); 
  void setLayeredPane(JLayeredPane layeredPane);
  void setContentPane(Container contentPane); 
}

Beispiele

Da die Ereignisbehandlung erst nachfolgend behandelt wird, reagiert der Top-Level-Container JFrame einzig auf das Ereignis »Schließen des Fensters« mit Beenden der Applikation (siehe Abb. 15.4).

JFrame-Beispiel:
JMenuBar + JLabel

package kap15;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class Test {
  public static void main(String[] args) {
    // neues Top-Level-Fenster anlegen
    JFrame jf= new JFrame();
    // Ereignis "Schließen der Frame" beendet 
Applikation
    jf.addWindowListener( new WindowAdapter() {
        public void windowClosing(WindowEvent e)
         { System.exit(0); }
    });

getRootPane()

    // eine Menüleiste hinzufügen
    JMenuBar jbar= new JMenuBar();
    jf.getRootPane().setJMenuBar(jbar);
    // einen Menüeintrag hinzufügen
    JMenu jmenu= new JMenu("Menü1");
    jbar.add(jmenu);
    // zwei Einträge zu Menü1 hinzufügen
    jmenu.add("1. Eintrag"); jmenu.add("2. Eintrag");

getContentPane()

    //auch möglich: JPanel jp= (JPanel) jf.getContentPane(); 

    Container jp= jf.getContentPane();
    // Eine Text in den contentPane einfügen
    jp.add(new JLabel("--- Komponente im contentPane ---"));
    // Anzeigen des Fensters
    jf.pack();
    jf.setVisible(true);           // show() ist deprecated!
  }
}

Alle Container verfügen über eine Methode add(), um andere Komponenten aufzunehmen.


Abbildung
Abbildung 15.4   Windows-Fenster zum Test-Programm

GlasPane-Beispiel

GlassPane-Variation: Im folgenden Test wird eine Komponente in glassPane eingefügt. Da glassPane über menuBar und contentPane liegt, werden die Komponenten in glassPane zum Schluss gezeichnet und überdecken eventuell die darunter liegenden (siehe Abb. 15.5).


Abbildung
Abbildung 15.5   glassPane überdeckt die anderen Container

Subklasse von JFrame:
reagiert auf Ereignis »Schließen«

Damit nicht immer wieder der Code für »Schließen des Fensters« hinzugefügt werden muss, wird eine Subklasse EJFrame von JFrame abgeleitet.

class EJFrame 
extends JFrame {
  public EJFrame() {
    addWindowListener(new WindowAdapter() {

windowClosing()

        public void windowClosing(WindowEvent 
e)
         { System.exit(0); }
    });
  }
}

Komponenten im glassPane überdecken alles

public class Test {
  public static void main(String[] args) {
    JFrame jf= new EJFrame();  // Kommentar siehe unten

getGlassPane()

    JPanel jg= (JPanel) jf.getGlassPane();
    jg.add(new JLabel("--- Komponente im 
glassPane ---"));
    jg.setVisible(true); // sonst unsichtbar
    Container jp= jf.getContentPane();
    jp.add(new JLabel("--- Komponente im contentPane ---"));
    jf.setSize(250,70);  // Fenstergröße manuell setzen
    jf.setVisible(true);
  }
}

Galileo Computing

15.3.2 JLayeredPane  downtop

Anordnung mittels Layers und Positionen

Der Container layeredPane von JRootPane ordnet alle Komponenten, die man mittels seiner speziellen add()-Methoden einfügt, in einem Stack an, sofern sie sich überlappen. Hierzu benutzt layeredPane Ebenen bzw. Layer und innerhalb der Ebenen wieder Positionen.

gp  Layer werden anhand Integer-Objekten geordnet, wobei Layer mit einer größeren Integer-Zahl über denen mit einer kleineren liegen.
gp  Komponenten innerhalb derselben Layer werden anhand einer int-Position geordnet, wobei die Komponente mit der Position 0 zuoberst liegt. Komponenten mit einer größeren Position liegen dann unter denen mit einer kleineren.

Beispiel

In der nächsten Test-Variation werden Schaltflächen zur besseren Sichtbarkeit in verschiedenen Ebenen und Positionen angeordnet. Da JLayeredPane keinen Layout-Manager benutzt, müssen die Komponenten ihre Größe und Pixel-Position selbst setzen.

class MyButton extends JButton {
  private static int num= 0;

JLayeredPane

  public MyButton (JLayeredPane lp, int layer, int 
pos) {
    setBounds(num*30,num*30,150,50);     // x,y, Breite, Höhe
    setForeground(Color.black);          // Schrift schwarz
    setText("Nr. "+ num++ +" Layer "+ layer+", Pos. "+ pos);
    lp.add(this,new Integer(layer),pos); 
// anordnen
  }
}

Anordnung von Schaltflächen mittels Layer- und Positionsangabe

public class Test {
  public static void main(String[] args) {
    JFrame jf= new EJFrame();            // EJFrame wie oben

getLayeredPane()

    JLayeredPane jlp= jf.getLayeredPane();
    new MyButton(jlp,3,1);  // obere Layer
    new MyButton(jlp,1,1);  // untere Layer, untere Position
    new MyButton(jlp,1,0);  // untere Layer, obere  Position
    new MyButton(jlp,2,1);  // mittlere Layer
    jf.setSize(300,180);
    jf.setVisible(true);
  }
}

Die Anordnung entspricht dann der in Abb. 15.6 dargestellten.

Layer und Positionen am Beispiel


Abbildung
Abbildung 15.6   Anordnung in Ebenen und Positionen bei JLayeredPane

DRAG_LAYER:
ganz oben

Die beiden Komponenten contendPane und menuBar werden in einer vordefinierten Ebene JLayeredPane.FRAME_CONTENT_LAYER (der zugehörige Wert ist -30000) eingeordnet, d.h. im Normalfall ganz unten.

CONTENT_LAYER:
ganz unten

Die oberste vordefinierte Ebene JLayeredPane.DRAG_LAYER (Wert: 400) ist dann Drag-Komponenten vorbehalten, die beim Ziehen über die Oberfläche immer über den anderen erscheinen müssen.


Galileo Computing

15.3.3 JWindow  downtop

JWindow: Fenster ohne Dekoration

JWindow, direkt abgeleitet von Window, ist ein rudimentäres Fenster ohne Dekoration (Fensterleiste, Größenanpassung, Schließen).

Damit sind die Einsatzmöglichkeiten recht gering und in der Regel auf Einsätze wie z.B. Startfenster begrenzt.

Beispiel

Zentrieren des Fensters auf dem Bildschirm

Diese Variation der Test-Klasse verwendet eine ScreenUtil-Klasse zur Zentrierung eines Windows auf dem Bildschirm. Des Weiteren werden der Font und die Größe des Textes manipuliert.

getDefaultToolkit(),
getScreenSize()

final class ScreenUtil {
  private ScreenUtil() {}
  public static void center(Window w, int 
width, int height) {
    Dimension scr= Toolkit.getDefaultToolkit().getScreenSize();
    w.setBounds((scr.width-width)/2,(scr.height-height)/2,
                 width,height);
  }
}

JWindow:
ein ideales Startfenster

public class Test {
  public static void main(String[] args) {
    JWindow jw= new JWindow();
    JLabel jl= new JLabel();

JLabel, Font:
setText(), setFont()

    // Font setzen: Font-Name, Stil, Größe
    jl.setFont(new Font("Bookman",Font.BOLD+Font.ITALIC,18));
    jl.setText("Startfenster");

setLayout(null)
setBounds()

    // ohne Layout-Manager: Größe und Position 
selbst setzen
    jw.getContentPane().setLayout(null);
    jl.setBounds(20,10,180,50);
    jw.getContentPane().add(jl);
    ScreenUtil.center(jw,150,70);
    jw.setVisible(true);
    // ... weitere Aktionen, Window schließen etc.
  }
}

Abbildung
Abbildung 15.7   JWindow, zentriert im Bildschirm

Toolkit-Klasse
mit nützlichen Operationen

Die abstrakte Klasse java.awt.Toolkit liefert mit Hilfe der statischen Methode getDefaultToolkit() eine Instanz von sich, die Informationen und Anpassungen an die Plattform/Hardware bereitstellt.

Die Toolkit-Instanz liefert u.a. Bildschirmauflösung und -größe, bietet Cursor-Anpassungen und Font-Informationen.

gp  Die Klasse Toolkit sollte selten direkt genutzt werden, da sie die Plattformunabhängigkeit nicht unbedingt fördert.

Font-
Anpassungen

Die Klasse java.awt.Font erlaubt alle Font-Familiennamen im Konstruktor, die das System bietet. Im zweiten Argument wird der Stil (als Konstanten Font.PLAIN, BOLD, ITALIC oder BOLD+ITALIC) und im dritten die Größe in Punkten übergeben.


Galileo Computing

15.3.4 JFrame vs. Frame  downtop

JFrame mit plattformspezifischer
Dekoration

Eine Instanz von JFrame oder Frame ist normalerweise das Hauptfenster einer Applikation. Beide Top-Level-Container bieten per Default plattformspezifisch einen Rahmen, Titelleiste mit Minimierungs-, Maximierungs- und Schließ-Schaltflächen.

Frame vs. JFrame

gp  Die Superklasse Frame aus dem AWT enthält kein JRootPane, d.h., in Frame werden die Komponenten direkt mit add() eingefügt.

JFrame enthält dagegen wie alle Top-Level-Container von Swing ein JRootPane und lässt das Einfügen von Komponenten nur in contentPane zu. Der Versuch, Komponenten in JFrame direkt mit add() einzufügen, endet mit einer Ausnahme.

Wie bereits bekannt, reagiert JFrame nicht automatisch auf das Ereignis »Schließen« mit dem Beenden der Applikation, sondern macht nur das Fenster unsichtbar.

Frame, JFrame:
subtile Unterschiede

Das folgende Code-Fragment öffnet ein AWT- und ein Swing-Fenster. Der Code für beide ist sehr ähnlich, jedoch gibt es subtile Unterschiede:

Frame:
setBackground(), add()

Frame f= new Frame();     // oder direkt Frame("Titel")
f.setSize(100,100);
f.setTitle("Titel");
f.setBackground(Color.green);  // direkt 
für die Frame
f.add(new Label("hallo"));     // direkt 
in die Frame
f.setVisible(true);
// Frame lässt sich per Default nicht schließen!
JFrame jf= new JFrame();  // oder direkt JFrame("Titel")
jf.setSize(100,100);
jf.setTitle("Titel");

JFrame:
setBackground(), add()

// jf.setBackground(Color.green);  wäre möglich, 

// aber nutzlos, da contentPane alles überdeckt.
jf.getContentPane().setBackground(Color.green);
jf.getContentPane().add(new JLabel("hallo"));
jf.setVisible(true);
// JFrame wird per Default beim Schließen nur unsichtbar!

Frame reicht
für einfache Java-Apps

gp  Für einfache Oberflächen und zur Schonung der Ressourcen reicht in der Regel ein Frame. Haben Flexibilität oder die Nutzung neuer Fähigkeiten Priorität, ist JFrame vorzuziehen.

WindowConstants

WindowConstants:
Anpassen des Schließverhaltens

JFrame zusammen mit JDialog und JInternalFrame implementieren das Interface WindowConstants, welches nur drei Konstanten definiert, um das Verhalten beim »Schließen« anzupassen.

public interface WindowConstants 
{
  int DO_NOTHING_ON_CLOSE= 0; // wie Frame
  int HIDE_ON_CLOSE= 1;       // default
  int DISPOSE_ON_CLOSE= 2;    // Ressourcen freigeben
}

setDefaultCloseOperation()

Ein bestimmtes Schließverhalten wird dann wie folgt gesetzt:

jf.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);

L&F: Look-and-Feels mit UIManager

UIManager:
L&F innerhalb eines Top-Level-Containers

Für die grafische Darstellung der Komponenten in einem Top-Level-Container wie JFrame stehen zumindest zwei bzw. drei verschiedene Oberflächen zur Verfügung.

Im folgenden Code-Fragment werden die verschiedenen L&F angezeigt und die erste gesetzt:

UIManager.LookAndFeelInfo[] lafArr= 
                       UIManager.getInstalledLookAndFeels();
for (int i= 0; i<lafArr.length; i++)
  System.out.print(lafArr[i].getName()+" ");
try {
  UIManager.setLookAndFeel(lafArr[0].getClassName());
} catch (Exception e) { System.out.println(e); }

Galileo Computing

15.3.5 JDialog und JOptionPane  downtop

JDialog:
spezialisiert auf Dialoge

Die Klasse JDialog ist ein Top-Level-Container, der auf Dialoge spezialisiert ist. Er hat deshalb auch keine Minimierungs- bzw. Maximierungs- Schaltflächen, sondern nur eine Schließ-Schaltfläche.

Modaler Modus

Des Weiteren gibt es einen Konstruktor, der das Fenster im modalen Modus öffnet, d.h., kein anderes Fenster derselben Applikation kann aktiv sein, bevor dieser Dialog nicht geschlossen wird.

Beispiel

Nachfolgend wird das L&F auf Motif gesetzt, der Dialog im Hauptfenster zentriert und das Schließverhalten auf DISPOSE_ON_CLOSE gesetzt.

setLookAndFeel()

try {
  UIManager.setLookAndFeel(
           "com.sun.java.swing.plaf.motif.MotifLookAndFeel");
} catch (Exception e) { System.out.println(e); }
JFrame jf= new JFrame("JFrame");
ScreenUtil.center(jf,200,150);    // siehe Beispiel in JWindow
jf.setVisible(true);

setLocationRelativeTo()

JDialog jd= new JDialog(jf,"JDialog",true);   // true= modal
jd.setSize(100,50);
jd.setLocationRelativeTo(jf);     // zentiert in jf   

setDefaultCloseOperation()

jd.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
jd.show();                        // wie setVisible(true)

Abbildung
Abbildung 15.8   JDialog, zentriert im zugehörigen Hauptfenster

show():
deprecated

gp  Die Superklasse Component im AWT enthält eine Methode show(), die als deprecated markiert ist. In der Regel sollte setVisible(true) verwendet werden.

Allerdings wird in einigen Subklassen wie z.B. Dialog die Methode show() wieder gültig überschrieben und wie in JInternalFrame mit zusätzlicher Funktionalität versehen.

JOptionPane

JOptionPane:
nützliche Dialog-Muster

Kurze Mitteilungen oder Abfragen sollten eine einheitliche Struktur aufweisen. Immer wieder den gleichen JDialog-Code zu schreiben, ist lästig und redundant.

Hier kommt die Klasse JOptionPane zur Hilfe. Sie ist direkt von JComponent abgeleitet und definiert bzw. implementiert eine Palette brauchbarer Dialog-Muster mit Hilfe von JDialog oder JInternalFrame (siehe Abb. 15.9).

Visuelles Äußeres von JOptionPane


Abbildung
Abbildung 15.9   Prinzipieller visueller Aufbau von JOptionPane

JOptionPane bietet Dialog-Schablonen

JOptionPane stellt für verschieden Aufgaben statische Methoden und Konstanten zur Verfügung, die für die meisten Aufgaben ausreichen. Sollte eine Instanz von JOptionPane wiederverwendet werden, können sie auch mit Hilfe passender Konstruktoren angelegt werden.

Die statischen Methoden decken vier Typen von Dialogen ab:

Bestätigungen

gp  Bestätigen/Confirm: Frage, Fehler, Warnung etc. mit diversen Schaltflächen.

Textangaben

gp  Eingabe/Input: Zeile, Liste etc. mit zwei Schaltflächen OK und Cancel.

Mitteilungen

gp  Mitteilung/Message: Mitteilung mit der Schaltfläche OK.

Optionen

gp  Option: Beliebige Daten mit Schaltflächen.

Drei statische Methoden sollen an kleinen Code-Fragmenten demonstriert werden.

Beispiele

Der folgende Dialog hat kein übergeordnetes Fenster (parentComponent ist null), hat einen String als Mitteilungs-Objekt, einen Titel, die Schaltfläche OK und ein Icon, das einen Fehler symbolisieren soll (siehe Abb. 15.10). Ist das übergeordnete Fenster null, wird der Dialog im Bildschirm zentriert.

showConfirmDialog()

System.out.println(
   JOptionPane.showConfirmDialog (null,
      "Mitteilung mit CLOSED_OPTION","ConfirmDialog",
      JOptionPane.CLOSED_OPTION,
      JOptionPane.ERROR_MESSAGE)
   );                                               // :: 0

Bestätigung mit JOptionPane im
Motif-L&F


Abbildung
Abbildung 15.10   JOptionPane.showConfirmDialog() im Motif-L&F

Die Methode showInputDialog() akzeptiert ein Array von Objekten als Auswahl, wobei als letztes Argument das per Default selektierte Objekt übergeben werden kann.

Die Darstellung der Objekte erfolgt in diesem Fall in einer JComboBox (siehe Abb. 15.11).

showInputDialog()

System.out.println(
  JOptionPane.showInputDialog (null,
      "Auswahliste","InputDialog",
      JOptionPane.QUESTION_MESSAGE, null,
      new String[] { "1.","2.","3.","4.","5.","6."},
      "3.")
  );                                  // Bei Cancel :: null 
                                      // Bei OK     :: 3.

Auswahlliste mit JOptionPane im
Motif-L&F


Abbildung
Abbildung 15.11   JOptionPane.showInputDialog() im Motif-L&F

Modaler
Options-Dialog

Im folgenden Beispiel wird ein Hauptfenster erzeugt, in dem das Option-Dialog-Fenster zentriert und modal geöffnet wird.

Die Darstellung der Objekte erfolgt in diesem Fall mit Schaltflächen. Bei einer Auswahl wird als Rückgabewert der Array-Index zurückgegeben, ansonsten der Wert -1 (siehe Abb. 15.12).

try {
  UIManager.setLookAndFeel(
    "com.sun.java.swing.plaf.windows.WindowsLookAndFeel");
} catch (Exception e) { System.out.println(e); }
JFrame jf= new JFrame("JFrame"); jf.show();

showOptionDialog()

String[] sarr= new String[] { "1.","2.","3.","4.","5.","6."};
System.out.println(
  JOptionPane.showOptionDialog(jf,
      "Optionsauswahl","OptionDialog",
      JOptionPane.DEFAULT_OPTION, 
      OptionPane.QUESTION_MESSAGE,null,sarr,null)
  );

Optionen mit JOptionPane in einem Hauptfenster im Windows-L&F


Abbildung
Abbildung 15.12   JOptionPane.showOptionDialog() im Windows-L&F

Die beiden null-Werte in showOptionDialog() bedeuten »kein eigenes Icon« und »keine Default-Selektion«.


Galileo Computing

15.4 Layout-Manager  downtop

Layout-Manager:
Design- Kompromisse

Container verfügen per Default über einen Layout-Manager, der die Aufgabe hat, die Komponenten, die hinzugefügt werden, im Inneren nach einem Schema anzuordnen.

gp  Layout-Manager sind immer ein Kompromiss zwischen optimalem Design und Flexibilität.

Alternative zum
XY-Design

Kennt man alle Umgebungs-Variablen wie Fenster-, Schrift- oder Randgrößen, führt eine exakte Positionierung und Größe der Komponenten zu einem optimalen Design der Oberfläche.

Layout-Manager: Anpassungen zur Laufzeit

Da die Oberfläche allerdings meistens auf unterschiedlichen Plattformen bzw. unterschiedlicher Hardware laufen soll, muss man die Arbeit einem Layout-Manager überlassen, der dann zur Laufzeit versucht, die Lage der Komponenten an die Umgebung (sub)optimal anzupassen.

Eigenschaften von Layout-Managern

Layout-Manager

gp  können für die meisten Container mit setLayout() frei gewählt werden.
gp  können auf null gesetzt werden, was bedeutet, dass man Position und Größe für jede Komponente selbst setzen muss, z.B. mit setBounds().
gp  können selbst entwickelt werden, d.h., sie müssen das Interface LayoutManager oder das Subinterface LayoutManager2 implementieren.
gp  werden vom Container bei allen Änderungen, die das Layout berühren, wie z.B. beim Einfügen und Entfernen von Komponenten aufgerufen.

Das AWT und Swing definieren bereits einige häufiger benötigte Layout-Schemata (siehe Abb. 15.13).

AWT- und Swing- Layout-Manager: interface-basierende Hierarchie


Abbildung
Abbildung 15.13   Layout-Manager im AWT und in Swing

Der LayoutManager2 erweitert also das ursprüngliche Interface um weitere Methoden, um unter anderem Constrains - zusätzliche Beschränkungen - spezifizieren zu können.


Galileo Computing

15.4.1 Überblick  downtop

Größeneigenschaften von JComponent

Jede Swing-Komponente JComponent hat eine minimale, maximale und bevorzugte Größe. Sie kann - sofern erforderlich - mit setMinimumSize(), setMaximumSize() bzw. setPreferredSize() gesetzt werden.

Bevorzugte Größe

Die letztgenannte bevorzugte Größe (Typ Dimension) gibt an, wie groß die Komponente sein sollte, um alle internen Elemente optimal darzustellen und ist deshalb für Layout-Manager von besonderer Bedeutung.

Icon

Bei einer Schaltfläche JButton berechnet sich die bevorzugte (und minimale) Größe z.B. aus der Textgröße und -länge, dem Textabstand zum Rand und der Randgröße selbst. Sie wird automatisch berechnet, wobei das Maximum praktisch unbegrenzt ist.

Wahl der Größe

gp  Lassen es die Umgebung und das Layout-Schema zu, wird der Layout-Manager die bevorzugte Größe der Komponente einhalten.

setSize(),
setBounds()

Versuche, z.B. mittels setSize() oder setBounds(), die Größe der Komponenten selbst zu bestimmen, scheitern immer daran kläglich, dass der Layout-Manager das letzte Wort hat.

Schemata der AWT-Layout-Manager

Tabelle 15.1   Layout-Manager im AWT
Layout-Manager Layout-Schema
FlowLayout Komponenten werden mit ihrer bevorzugten Größe wie Wörter in Zeilen angeordnet, wobei diese umbrechen können. Wie bei Text kann die Zentrierung mit LEFT, RIGHT oder CENTER angegeben werden.
GridLayout Komponenten werden ohne Rücksicht auf ihre bevorzugte Größe in die Zellen einer Matrix eingeordnet.
BorderLayout Komponenten können in die Kompass-Bereiche NORTH, SOUTH, EAST, WEST oder in das Zentrum CENTER eingefügt werden. Die bevorzugte Größe wird abhängig vom Bereich berücksichtigt.
CardLayout Jede Komponente ist so groß wie der Container und zu einem Zeitpunkt ist immer nur eine Komponente sichtbar.
GridBagLayout Ein GridLayout, in dem Reihen und Spalten unterschiedliche Höhen oder Breiten haben können. Komponenten können eine oder mehrere Zelle(n) einnehmen, wobei die Informationen beim add() an eigene Instanzen der Klasse GridBagConstraints übergeben werden.

Alle in Swing definierten Layout-Manager sind für spezifische Komponenten definiert, wobei das BoxLayout allerdings auch generell nützlich sein kann.

Schemata der Swing-Layout-Manager

Tabelle 15.2   Spezialisierte Layout-Manager in Swing
Layout-Manager Layout-Schema
ScrollPaneLayout Spezialisiert für den JScrollPane-Container, definiert dieses Layout neun Bereiche, in die ein »Lauf«-Bereich eingeteilt werden kann. Neben dem eigentlichen Laufbereich JViewPort sind dies die vier Ecken, die Zeilen- und Spaltenköpfe und die vertikalen und horizontalen Rollbalken.
ViewportLayout Spezialisiertes Layout für JViewPort (siehe oben).
BoxLayout Dieses Layout, benutzt vom Box-Container, fügt die Komponenten entweder horizontal von links nach rechts oder vertikal von oben nach unten in einer Reihe/Box ein. Dabei beachtet es im Gegensatz zum GridLayout die bevorzugte Größe der Komponenten.
OverlayLayout Überlagert Komponenten anhand eines Ausrichtungspunkts und wird u.a. vom JMenuItem für Text und Icons benutzt.

Ausgangspunkt für einen optimalen Entwurf ist ein idealer Bildschirm mit exakt positionierten Komponenten idealer Größe, mit anderen Worten ein typisches XY-Design.

Es gibt - bis auf triviale Anordnungen - keinen einzelnen Layout-Manager, den man der contentPane eines Top-Level-Fensters zuweisen könnte, um dieses XY-Design nachzubilden. Deshalb gilt:

Kunst der Komposition von Layout-Managern

gp  Die Kunst besteht darin, eine geeignete Komposition von passenden Layouts zu finden, die dem idealen Design am nächsten kommt.

Hierzu muss man sicherlich die Wirkung der o.a. Layouts im Detail kennen, um Container mit passenden Layout-Managern entsprechend zusammenzusetzen.


Galileo Computing

15.4.2 BorderLayout und FlowLayout  downtop

Im folgenden Code-Fragment wird die Wirkung eines Border-Layouts mit einem eingebetteten Container mit Flow-Layout demonstriert.

Border-Layout mit internem Flow-Layout

JFrame jf= new EJFrame("Border/Flow-Layout"); jf.setSize(200,200); 
                      // siehe Erklärung
Container cp= jf.getContentPane();

setLayout()

//cp.setLayout(new BorderLayout()); 
überflüssig, da default! cp.add(new JButton("Nord"),BorderLayout.NORTH);
cp.add(new JButton("Süd"),BorderLayout.SOUTH);
cp.add(new JButton("West"),BorderLayout.WEST);
cp.add(new JButton("Ost"),BorderLayout.EAST);
Container jp= new JPanel();
//jp.setLayout(new FlowLayout()); 
  überflüssig, da default!
for (int i= 0; i<9; i++) jp.add(new JButton("Nr. "+(i+1)));
jp.setSize(100,50);                         // ohne 
Wirkung!
cp.add(jp,BorderLayout.CENTER);

pack()

//jf.pack();        
                   siehe Kommentar unten!
jf.setVisible(true);

Reaktionen von Border- und Flow-Layout zur Laufzeit


Abbildung
Abbildung 15.14   Border-Layout mit Flow-Layout im Center

Erklärung: Die Größe des Top-Level-Fensters lässt im Center-Bereich nur eine Schaltfläche pro Zeile des Flow-Layouts zu (Abb. 15.14 oben links).

Sollte jf.pack() in der vorletzten Zeile ausgeführt werden, wird die Größe des Top-Level-Fensters anhand der inneren Komponenten bestimmt, d.h., jp.setSize() in der zweiten Zeile wird wirkungslos (Abb. 15.14 unten).

Eine Größenänderung des Top-Level-Fensters durch den Benutzer führt dann zu diversen anderen Anordnungen (Abb. 15.14 oben rechts).


Galileo Computing

15.4.3 CardLayout  downtop

CardLayout:
Karten nebeneinander angeordnet

Bei Containern mit CardLayout kann mittels add(Component c, Object constraints) jeweils eine Komponente pro Karte eingefügt werden. Das Objekt constraints muss dabei ein String sein, der die Aufgabe hat, die Karte zu identifizieren (kann aber auch "" gesetzt werden).

Kartenwechsel

CardLayout enthält dann fünf Methoden, die Karten zu wechseln:

   public void first   (Container parent);
   public void last    (Container parent); 
   public void next    (Container parent);
   public void previous(Container parent);
   public void show    (Container parent, String name);

Die contendPane wird nachfolgend auf CardLayout gesetzt und es werden drei Bilder eingefügt. Mittels next() werden dann die Karten gewechselt.


Abbildung
Abbildung 15.15   CardLayout mit drei »Karten«

setIcon()
ImageIcon next()

JFrame jf= new EJFrame("3 Cards");
Container cp= jf.getContentPane();
CardLayout cl; 
cp.setLayout(cl= new CardLayout());
JLabel im;
for (int i= 0; i<3; i++) {
  im= new JLabel();     // langweilige Version, siehe GridLayout
  im.setIcon(new ImageIcon("C:/Scan/bild"+i+".gif"));
  cp.add(im,"image"+i);
}
jf.pack();
jf.setVisible(true);
while (true) {
  try { Thread.sleep(2000); } catch (Exception e) {}
  cl.next(cp);  // wechselt am Ende zur 
ersten Karte
}

Galileo Computing

15.4.4 GridLayout  downtop

GridLayout:
grafische Matrix mit einheitlichen Zellen

Wie bei einer Matrix, wird bei der Anlage eines GridLayouts in der Regel die Anzahl der Zeilen und Spalten angegeben.

Da es sich um eine »grafische« Matrix handelt, können die Abstände in Pixel zwischen den Zellen angegeben werden. Die beiden ersten Konstruktoren sind Spezialfälle des letzten unten angeführten, d.h. rufen diesen über this(..) auf.

GridLayout-Konstruktoren

public GridLayout();                   // this(1, 
0, 0, 0);
public GridLayout(int rows, int cols); // this(rows,cols,0,0)
public GridLayout(int rows, int cols, int hgap, int vgap);

Das folgende Code-Fragment ist nur eine einfache Variation des Beispiels in CardLayout.


Abbildung
Abbildung 15.16   Gridlayout mit vier Zellen
JFrame jf= new EJFrame("GridLayout");
Container cp= jf.getContentPane();

Bildübergabe an
JLabel-Konstruktor

cp.setLayout(new GridLayout(2,2,10,10));
for (int i= 0; i< 3; i++) 
  cp.add(new JLabel(new ImageIcon("C:/Scan/bild"+i+".gif")));
cp.add(new JButton("Schaltfläche"));
jf.pack();
jf.setVisible(true);

JLabel kann im Konstruktor sofort ein Bild übergeben werden. Alle Komponenten bekommen die gleiche Größe, und zwar die der größten Komponente.


Galileo Computing

15.4.5 GridBagLayout  downtop

GridBagLayout:
große Flexibilität vs. Codier-Aufwand

Das GridBagLayout ist aufgrund der GridBagConstraints-Instanz, die bei add() mit jeder Komponente übergeben wird, sehr flexibel.

GridBagConstraints:
Steuerung mittels elf Parametern

gp  Der Aufwand für das Einfügen einer Komponente mit Hilfe eines GridBagLayout ist in der Regel größer als beim null-Layout.

Mit Hilfe von elf Parametern(!), gekapselt in einer GridBagConstraints-Instanz, wird das Verhalten jeder einzelnen Komponente definiert.

public GridBagConstraints();           // setzt Default-Werte
public GridBagConstraints(int gridx,int gridy,
 int gridwidth,int gridheight, double weightx,double weighty,
 int anchor, int fill, Insets insets, int ipadx,int ipady)

Übersicht über GridBagConstraints- Parameter

Tabelle 15.3   Positionierung von Komponenten mit GridBagConstraints
Parameter Bedeutung
gridx
gridy
Zeile/Spalte in der Matrix. Die Konstante RELATIVE (der Default-Wert) bedeutet bei x bzw. y rechts bzw. unter der letzten eingefügten Komponente.
gridwidth
gridheight
Anzahl der benötigten Zellen in der Zeile/Spalte (Default-Wert: 1,1). RELATIVE zeigt die vorletzte Komponente der Zeile bzw. Spalte an. REMAINDER bedeutet die letzte Komponente, die alle rest